﻿(*   Copyright 2011 Alexander Omelchuk
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *)

namespace Lisp
open System
open System.Reflection
open System.Linq.Expressions

//TODO - add key and AOK to lambda list
type LambdaList(req, opt, rest, aux, takeDEnv) =
  ///<summary>List of of required parameters names</summary>
  member ll.Req  : string list = req
  ///<summary>List of (name*defaultValue*pname) of optional parameters</summary>
  member ll.Opt  : (string*SExpr*string) list = opt
  ///<summary>Name of rest parameter</summary>
  member ll.Rest : string option = rest
  ///<summary>List of (name*initform) of aux parameters</summary>
  member ll.Aux  : (string*SExpr) list = aux
  ///<summary>Names of parameters for generated function.</summary>
  member ll.Args = Seq.toArray (seq {
    yield! req
    yield! List.choose (fun (_,_,pname) -> if String.IsNullOrEmpty(pname) then None else Some pname) opt
    yield! List.map (fun (name,_,_) -> name) opt
    if rest.IsSome then yield rest.Value
    })
  member ll.ToFuncArgs env (args:'a[]) = seq {
    let takeRest count (args:'a[]) = args.[count .. args.Length-1]
    let argT, argNil, makeDefValue, makeRest =
      match typeof<'a> with
      | x when x = typeof<SExpr> ->
         T :?> 'a, Nil :?> 'a,
         (fun (expr:SExpr) -> expr :?> 'a),
         (fun args -> (Lst.ofSeq args) :?> 'a)
      | x when x = typeof<Expression> ->
         Expression.Constant(T, typeof<SExpr>) :> obj :?> 'a,
         Expression.Constant(Nil, typeof<SExpr>) :> obj :?> 'a,
         (fun expr -> Expression.Constant(expr, typeof<SExpr>) :> obj :?> 'a),
         (fun args ->
          Expression.Call(
            ofSeqMethodInfo,
            Expression.Constant(Seq.cast<Expression> args)) :> obj :?> 'a)
      | _ -> raise (NotSupportedException())      

    if takeDEnv then yield env
    // set required args
    let reqCount = req.Length
    if args.Length < reqCount then raiseArgError tooFewParamsFun
    yield! args.[0..reqCount-1]
    let args = takeRest reqCount args
    
    let optHaveCount = min opt.Length args.Length
    // set pnames
    yield!
      (List.mapi (fun i (_,_,pname) ->
                    if not (String.IsNullOrEmpty(pname))
                    then Some (if i < optHaveCount then argT else argNil)
                    else None)
                 opt
       |> List.choose id)
    // set optional args
    yield!
      (List.mapi (fun i (name,def,_) -> 
                    if i < optHaveCount then args.[i] else makeDefValue def)
                 opt)
    let args = takeRest optHaveCount args

    // set rest
    match rest with
    | Some rest -> yield makeRest args
    | None -> if args.Length > 0 then raiseArgError tooManyParamsFun
   }

type Fun =
  ///<summary>"Almost" compiled function.</summary>
  | Function of LambdaList * Expression
  ///<summary>Lisp special operator (it's arguments are passed without evaluation)</summary>
  | SpecialOp of (SExpr -> Expression)
  ///<summary>Lisp macro - evaluated in compile time, result is compiled again.</summary>
  | Macro of LambdaList * Delegate